package com.lingyun.common.utils.jwt;

import com.lingyun.common.utils.StringUtils;
import com.lingyun.common.utils.UUID;
import com.lingyun.common.utils.redis.RedisUtils;
import com.lingyun.common.vo.LoginUser;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Component
public class JwtUtils {

    // 20分钟
    private static final long MILLIS_MINUTE_TEN = 20 * 60 * 1000;

    // 令牌自定义标识
    @Value("${jwt.TokenHeader}")
    private String header;

    // 密钥
    @Value("${jwt.secret}")
    private String secret;

    // 令牌有效期（一小时这里的单位是毫秒）
    @Value("${jwt.expiration}")
    private Integer expiration;

    @Value("${jwt.login_user_key}")
    private String login_user_key;

    @Resource
    RedisUtils redisUtils;




    /**
     * token是否有效
     *
     * @param token
     * @param userDetails
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }


    /**
     * 从token获取用户信息
     *
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token) {
        String username = null;
        Claims claims = parseToken(token);
        try {
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }





    /**
     * 根据荷载生成JWTToken
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).setExpiration(generateExpiration()).signWith(SignatureAlgorithm.HS512, secret).compact();
    }



    /**
     * 判断token是否可以被刷新
     *
     * @param token
     * @return
     */
    public Boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }
    /**
     * 判断token是否失效
     *
     * @param token
     * @return
     */
    private boolean isTokenExpired(String token) {
        Date expireDate = getExpiredDateFromToken(token);
        //如果token有效的时间在当前时间之前就表示实效
        return expireDate.before(new Date());
    }

    /**
     * 从token中获取实效时间
     *
     * @param token
     * @return
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getExpiration();
    }



    /**
     * 生成token实效时间
     * @return 当前时间+配置时间
     */
    private Date generateExpiration() {
        return new Date(System.currentTimeMillis() + expiration);
    }


    /***********************************************************************************************************************************************************************/




    /** 1
     * 从token中获取用户身份信息
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token)) {
            Claims claims = parseToken(token);
            if(!StringUtils.isNull(claims)){
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(login_user_key);
                String userKey = "key_toke_"+uuid;
                return redisUtils.getCacheObject(userKey);
            }
        }
        return null;
    }


    /** 2
     * 从令牌中获取数据声明(根据token获取荷载)
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e){
            System.out.println("jwt过期");
        }catch (Exception e) {
            e.printStackTrace();
        }
        return claims;
    }


    /** 3
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     * @param loginUser 用户信息
     */
    public void verifyToken(LoginUser loginUser) {
        // 过期时间
        long expireTime = loginUser.getExpireTime();
        // 当前时间
        long currentTime = System.currentTimeMillis();

        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            createToken(loginUser);
        }
    }


    /**
     *
     * @param loginUser 登录信息
     * @return token
     */
    public String createToken(LoginUser loginUser) {
        //用户唯一标识
        String token = UUID.generateShortUuid();
        loginUser.setToken(token); //设置用户唯一标识


        loginUser.setLoginTime(System.currentTimeMillis()); //设置登录时间
        loginUser.setExpireTime(loginUser.getLoginTime() + expiration); // 设置过期时间
        // key
        String userKey = "key_toke_"+token;
        // 存入redis
        redisUtils.setCacheObject(userKey, loginUser, expiration, TimeUnit.MILLISECONDS);
        // 声明数据
        Map<String, Object> claims = new HashMap<>();
        claims.put(login_user_key, token);
        return createToken(claims,loginUser.getUsername());
    }
    /** 2
     *
     * @param claims  声明数据
     * @param name  用户名字
     * @return 返回 token
     */
    private String createToken(Map<String, Object> claims ,String name) {
        // 生成token
        String token = Jwts.builder()
                //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setClaims(claims)
                // JWT的唯一标识(根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击)  {"jti":""}
                .setId(header)
                // 签发用户       {"sub":""}
                .setSubject(name)
                // 签发时间       {"iat":""}
                .setIssuedAt(new Date())
                // 过期时间设置    {"exp":""}
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS256, secret).compact();
        return token;
    }


    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(login_user_key)) {   //startsWith() 方法用于检测字符串是否以指定的前缀开始。
            token = token.replace(login_user_key, "");
        }
        return token;
    }




}
